<template>
  <div class="container" ref="container"></div>
</template>

<script setup>
import { onMounted, onUnmounted, ref } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import gsap from "gsap";

let controls = null;
let container = ref(null);
const width = window.innerWidth;
const height = window.innerHeight;

// 创建场景
const scene = new THREE.Scene();

// 创建相机
const cameraY = 15;
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
camera.position.set(0, cameraY, 10);
camera.lookAt(0, 0, 0);

// 创建渲染器
const renderer = new THREE.WebGL1Renderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor("#262837");

// 环境光
const ambientLight = new THREE.AmbientLight("#ffffff", 0.5);
scene.add(ambientLight);

// 月光
const moonLight = new THREE.DirectionalLight("#f5f5f5", 0.5);
moonLight.position.set(2, 7, -4);
scene.add(moonLight);

// 地板
const floor = new THREE.Mesh(
  new THREE.PlaneGeometry(50, 50),
  new THREE.MeshStandardMaterial({ color: "#ffffff", side: THREE.DoubleSide })
);
floor.rotation.x = -Math.PI / 2;
floor.position.y = -0.01;
scene.add(floor);

// 球体
const ball = new THREE.Mesh(
  new THREE.SphereGeometry(2, 32, 32),
  new THREE.MeshBasicMaterial({ color: "#777777" })
);
ball.position.set(4, 5, 0);
scene.add(ball);

// 添加立方体
let backMaterial = new THREE.MeshPhysicalMaterial({
  map: new THREE.TextureLoader().load("static/111.png"),
  side: THREE.DoubleSide,
});
let plainMaterial = new THREE.MeshBasicMaterial({
  color: 0xff0000,
  transparent: true,
  opacity: 0.4,
});
const boxMaps = [
  plainMaterial,
  plainMaterial,
  plainMaterial,
  plainMaterial,
  plainMaterial,
  backMaterial,
]; // 创建纹理数组，给某个面特殊处理，方便观察移动朝向是否正确
const cube = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), boxMaps);
cube.position.set(0, 0.5, 0);
scene.add(cube);

/**
 * animate：gsap动画库
 */
class MoveAndTurnControl {
  constructor(animate) {
    this.animate = animate;
    this.isMoving = false;
    this.createTL();
  }

  /**
   * 更新位置和朝向
   * @param {*} param0
   * param0.target 被控制的物体对象 <模型 | object3D | group | mesh对象>
   * param0.destPosition 被控物体要移动到的三位坐标 <THREE.Vector3>
   * param0.speed 移动速度
   * param0.turnSpeed 物体转向速度
   * param0.fixedY <Number> 设置被控物体的Y轴高度，0代表不控制，则物体的Y会等于destPosition.y + offsetY
   * param0.offsetY <Number> Y轴偏移量，fixedY==0时有效
   * param0.onStart 动作开始时回调
   * param0.onUpdate 动作执行过程中回调
   * param0.onComplete 动作结束时回调
   */
  go({
    target,
    destPosition,
    speed = 5,
    turnSpeed = 0.1,
    fixedY = 0,
    offsetY = 0.5,
    onStart,
    onUpdate,
    onComplete,
  }) {
    // 如果正在移动，结束当前动画，从新定义新的timeline
    if (this.isMoving) {
      this.timeline.kill();
      this.createTL();
    }

    let targetPosition = target.position.clone();
    let offsetAngle = 0; // 2 * Math.PI 等于 0 //目标移动时的朝向偏移
    console.log("移动速度:", speed);

    // 计算物体移动的距离
    let moveDistance = Math.sqrt(
      Math.pow(destPosition.x - targetPosition.x, 2) +
      Math.pow(destPosition.z - targetPosition.z, 2)
    );
    console.log("移动距离:", moveDistance);
    let t = moveDistance / speed;
    console.log("移动时间:", moveDistance);

    let mtx = new THREE.Matrix4(); //创建一个4维矩阵
    mtx.lookAt(targetPosition, destPosition, target.up); // 设置朝向
    mtx.multiply(
      new THREE.Matrix4().makeRotationFromEuler(
        new THREE.Euler(0, offsetAngle, 0)
      )
    );
    let toTurn = new THREE.Quaternion().setFromRotationMatrix(mtx); // 计算出需要进行旋转的四元数值

    // 动画：移动位置+旋转朝向
    onStart && onStart();
    this.isMoving = true;
    this.timeline.to(target.position, {
      duration: t,
      x: destPosition.x,
      y: fixedY ? fixedY : destPosition.y + offsetY,
      z: destPosition.z,
      onUpdate: () => {
        // 边移动边旋转
        target.quaternion.slerp(toTurn, turnSpeed);
        onUpdate && onUpdate(target.position, toTurn);
      },
      onComplete: () => {
        this.isMoving = false;
        onComplete && onComplete(target);
      },
    });
  }

  createTL() {
    this.timeline = this.animate.timeline();
  }
}

// 鼠标事件，射线碰撞检测获取目标点空间坐标
let raycaster = new THREE.Raycaster();
let mouseVector = new THREE.Vector3();
let MTCtrl = new MoveAndTurnControl(gsap);
function onMouseDown(evt) {
  // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
  mouseVector.x = (evt.clientX / width) * 2 - 1; //-1,1
  mouseVector.y = -((evt.clientY / height) * 2 - 1); //-1,1
  mouseVector.z = 1;

  // 通过摄像机和鼠标位置更新射线（将平面坐标转为世界坐标）
  raycaster.setFromCamera(mouseVector, camera);

  // 计算物体和射线的焦点
  let raycasters = raycaster.intersectObjects(scene.children, false);
  if (raycasters.length > 0) {
    let intersectObj = raycasters[0];
    let { point, distance } = intersectObj;

    // 方法1：controls.getAzimuthalAngle()获取控制器的水平旋转
    // distance = Math.min(intersectObj.distance, 10);
    // gsap.timeline().to(cube.position, {
    //     duration: distance/20,
    //     x: intersectObj.point.x,
    //     // y: intersectObj.point.y,
    //     z: intersectObj.point.z,
    //     onComplete: () => {
    //         console.log(controls.getAzimuthalAngle())
    //         cube.rotation.y = controls.getAzimuthalAngle()
    //     }
    // })

    // 方法2：根据物体目标点的x和z计算tan
    // let turnAngle = Math.atan(point.x / point.z)+Math.PI;
    // console.log('turnAngle:', turnAngle)

    // gsap.timeline().to(cube.rotation, {
    //     duration: 0.3,
    //     // y: controls.getAzimuthalAngle(),
    //     y: turnAngle,
    //     onComplete: () => {
    //         gsap.timeline().to(cube.position, {
    //             duration: t,
    //             x: intersectObj.point.x,
    //             // y: intersectObj.point.y,
    //             z: intersectObj.point.z,
    //         })
    //     }
    // })
    // return

    // 方法3：计算物体坐标和目标点坐标的空间向量夹角
    // let v1 = new THREE.Vector3(cube.position.x, cube.position.y, cube.position.z)
    // let v2 = new THREE.Vector3(point.x, point.y, point.z)
    // let turnAngle = v1.cross(v2)
    // console.log(turnAngle)
    // gsap.timeline().to(cube.rotation, {
    //     duration: 0.3,
    //     // x: v1.x,
    //     y: v1.y,
    //     // z: v1.z,
    // })
    // return

    // 方法4：四元数
    MTCtrl.go({
      target: cube,
      destPosition: point,
      onStart: () => {
        controls.enabled = false;
      },
      onComplete: () => {
        controls.enabled = true;
      },
    });
  }
}

window.addEventListener("mousedown", onMouseDown, false);

function animate() {
  camera.position.y = cameraY; // 视角高度锁定

  controls.update(); // 更新控制器
  controls.target.copy(cube.position); //(物体视角跟随)

  renderer.render(scene, camera);
  window.requestAnimationFrame(animate);
}

const reRender = () => {
  // 修改相机的参数，宽高比
  camera.aspect = window.innerWidth / window.innerHeight;
  // 更新投影的变换矩阵
  camera.updateProjectionMatrix();
  // 重新设置渲染器尺寸
  renderer.setSize(window.innerWidth, window.innerHeight);
};

// 挂载之后获取dom
onMounted(() => {
  // 添加轨道控制器，并锁定视角到被控物体上
  controls = new OrbitControls(camera, container.value);
  controls.enableDamping = true;
  controls.enablePan = false;
  controls.enableRotate = false;
  controls.maxDistance = 20;
  controls.minDistance = 20;

  container.value.appendChild(renderer.domElement);
  animate();

  window.addEventListener("resize", () => reRender);
});

onUnmounted(() => {
  window.removeEventListener("resize", reRender);
});
</script>
<style lang="less" scoped>
.container {
  width: 100vw;
  height: 100vh;
  border: solid 4px hsla(160, 100%, 37%, 1);
}
</style>